﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace SuperKit
{
    /*
     * 这个项目使两个（或多个）文件进行合并。选择每一个文件中的一个片断进行混剪。
     * 比如有两个60秒的动画，如果时间设置为10。那么取第一个项目的0-10，20-30，40-50，另外一个项目的10-20，30-40，50-60。
     * 这个通常用来处理如果一个视频合集中，有不同衣服版本的情况，如果保留全部衣服，那么需要的空间就大了。
     * 特别用法：
     * 如果项目中有一个文件，也可以把这个文件切成N份。
     */
    class MixCombineMovie : CommandExecutor {


        private class Pattern {
            public int start;
            public int end;

            public Pattern(int start, int end) {
                this.start = start;
                this.end = end;
            }
        }

        private class MovieFile {
            public FileInfo file { get; }
            public int Index { get; }       
            public List<Pattern> Patterns { get; }
            public int Length { get;  }
            public float Rate { get; internal set; }

            public MovieFile(FileInfo file, int index) {
                this.file = file;
                Length = GetMediaTimeLenSecond(file.FullName);
                Index = index;
                Patterns = new List<Pattern>();
            }

            public void Add(int start, int end) {
                Patterns.Add(new Pattern(start, end));
            }

            public Pattern GetPattern(int index) {
                if (index < 0 || index >= Patterns.Count) {
                    return null;
                }
                return Patterns[index];
            }
        }

        private const string SPLIT_DIR = "(SPLIT)";

        private string name;
        private int time;
        // 当这个项目为TRUE的时候，移除中途产生的序列文件。当如果要保留文件，或者把这个命令当成分割文件使用，那么可以选择为False。
        private bool keepWIP;
        List<MovieFile> files;
        private int Size => files.Count;
        private string ext;
        private int queueIndex;
        private int maxPattern;
        private DirectoryInfo splitDir;
        private List<string> splitFiles;
        private string outputPath;
        private string outputText;
        private float fullLength;

        public MixCombineMovie(ArgReader arg, IExecutorOwner form, string fpath) : base(arg, form, fpath) {
            name = arg.GetParameter("-name");
            time = arg.GetInteger("-time", 60);
            keepWIP = arg.IsContain("-keepWIP");
            files = new List<MovieFile>();
        }

        public override void Execute() {
            base.Execute();

            DirectoryInfo info = new DirectoryInfo(FilePath);

            // 获得用来存放序列的目录。
            splitDir = new DirectoryInfo(Path.Combine(FilePath, SPLIT_DIR));
            if (splitDir.Exists == false) {
                splitDir.Create();
            }

            // 获得文件，筛选其中的文件，并且得到总长度。
            var arr = info.GetFiles();
            int index = 0;
            fullLength = 0;
            foreach (var i in arr) {
                if (Kit.IsFileType(i, Kit.MEDIA_EXT)) {
                    var item = new MovieFile(i, index++);
                    files.Add(item);
                    fullLength += item.Length;
                    // 选择其中任何一个扩展名即可。如果扩展名不一样，注定是失败的，所有任何一个就行了。
                    ext = Path.GetExtension(i.Name); 
                }
            }
            
            // 计算每个文件在总长度中的比率
            foreach (var i in files) {
                i.Rate = i.Length / fullLength;
            }

            // 拆分序列
            maxPattern = 0;
            for (int i = 0; i < files.Count; i++) {
                Process(files[i], i);
                maxPattern = Math.Max(maxPattern, files[i].Patterns.Count);
            }

            Thread t = new Thread(ExecuteSplit);
            t.Start();
        }

        private void ExecuteSplit() {
            splitFiles = new List<string>();

            // 将每一个序列拆分成完成的序号时间
            queueIndex = 0;
            for (int i = 0; i < maxPattern; i++) {
                for (int idx = 0; idx < files.Count; idx++) {
                    AddToQueue(files[idx], files[idx].GetPattern(i));
                }
            }

            // 执行合并
            ExecuteCombineMovie();

            // 清理剩下的文件
            ClearWIP();

            End();
        }

        private void ClearWIP() {
            File.Delete(outputText);
            if (keepWIP == false) {
                splitDir.Delete(true);
            }
        }

        private void AddToQueue(MovieFile file, Pattern pattern) {
            if (pattern == null) {
                return;
            }

            Flush($"[{queueIndex}]{file.file.Name} {FormatTime(pattern.start)} => {FormatTime(pattern.end)}");

            string path = Path.Combine(splitDir.FullName, String.Format("{0:D4}{1}", queueIndex + 1, ext));
            string cmd = string.Format("-ss {0} -to {1} -accurate_seek -i \"{2}\" -c copy -avoid_negative_ts 1 \"{3}\" -y", 
                FormatTime(pattern.start), FormatTime(pattern.end), file.file.FullName, path);

            // 将文件加入到列表
            splitFiles.Add(path);

            Flush($"CMD:{cmd}");

            ExecuteFFMpeg(cmd, true);

            queueIndex++;
        }

        private void ExecuteCombineMovie() {
            // 直接将这个目录名加一个扩展名，变成一个文件。
            outputPath = string.Format("{0}{1}", FilePath, ext);

            // 拼接用于合并的文件列表
            StringBuilder sb = new StringBuilder();
            foreach (var i in splitFiles) {
                sb.Append(string.Format("file '{0}'\r\n", i));
            }
            outputText = string.Format("{0}.txt", FilePath);
            File.WriteAllText(outputText, sb.ToString());

            // 执行拼接
            var cmd = string.Format("-f concat -safe 0 -i \"{0}\" -c copy \"{1}\" -y", outputText, outputPath);

            ExecuteFFMpeg(cmd, false);

            Flush($"Combin CMD:{cmd}");
        }

        private string FormatTime(int second) {
            int secondLeft = second % 60;
            int minute = second / 60;
            int minuteLeft = minute % 60;
            int hour = minute / 60;
            return string.Format("{0:D2}:{1:D2}:{2:D2}.0", hour, minuteLeft, secondLeft);
        }

        public static string ExecuteFFMpeg(string cmd, bool noWindow) {
            Process p = new Process();
            //设置要启动的应用程序
            p.StartInfo.FileName = ConfigReader.Instance.FFMpegExe;
            p.StartInfo.Arguments = cmd;
            //是否使用操作系统shell启动
            p.StartInfo.UseShellExecute = false;
            // 接受来自调用程序的输入信息
            //p.StartInfo.RedirectStandardInput = true;
            //输出信息
            //p.StartInfo.RedirectStandardOutput = true;
            // 输出错误
            //p.StartInfo.RedirectStandardError = true;
            //不显示程序窗口
            p.StartInfo.CreateNoWindow = noWindow;
            //启动程序
            p.Start();            

            //等待程序执行完退出进程
            p.WaitForExit();
            p.Close();

            return Kit.EMPTY;
        }

        private void Process(MovieFile file, int index) {
            var length = file.Length;
            // 1、计算每个片断的总时间。这是和时间、总文件数相关的。比如10秒，2个文件，则位移为20秒，即一个文件选择10秒，
            // 共同组成一个20秒的片断。很显然，如果文件数变成3，那么3个10秒就构成一个Pattern30秒的视频。
            int patternLength = time * Size;
            // 2、在每个Pattern中的时间位移。比如一个Pattern是30秒。那么第2个文件开始就是时间*索引，是从10-20秒。
            // 在这个基础上，追加文件
            int patternOffset = time * index;
            // 根据索引决定文件的片断。
            int pattern = 0;
            while (true) {
                // 得到起点的算法如下 = Pattern起始时间 + Pattern位移
                int start = Math.Min(patternLength * pattern + patternOffset, length);
                // 结束时间计算很简单，直接加上时间数即可，如果超出视频时间，跳出
                int end = Math.Min(start + time, length);

                // 如果已经没有视频时间了，跳出
                if (start == end) {
                    break;
                }

                // 将当前的Pattern加入序列
                file.Add(start, end);

                // 下一个Pattern步进。
                pattern++;
                
                if (end >= length) {
                    break;
                }
            }
        }

        public static string GetMediaTimeLen(string path) {
            throw new NotImplementedException();
            //var mediaDet = (IMediaDet)new MediaDet();
            //DsError.ThrowExceptionForHR(mediaDet.put_Filename(path));// find the video stream in the fileint index;
            //var type = Guid.Empty;
            //for (int index = 0; index < 1000 && type != MediaType.Video; index++) {
            //    mediaDet.put_CurrentStream(index);
            //    mediaDet.get_StreamType(out type);
            //}
            //// retrieve some measurements from the video
            //double frameRate;
            //mediaDet.get_FrameRate(out frameRate);
            //var mediaType = new AMMediaType();
            //mediaDet.get_StreamMediaType(mediaType);
            //var videoInfo = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.formatPtr, typeof(VideoInfoHeader));
            //DsUtils.FreeAMMediaType(mediaType);
            //var width = videoInfo.BmiHeader.Width;
            //var height = videoInfo.BmiHeader.Height;
            //double mediaLength;//这个是视频长度，单位秒
            //mediaDet.get_StreamLength(out mediaLength);
            //var frameCount = (int)(frameRate * mediaLength);
            //var duration = frameCount / frameRate;
            //return "";
        }

        /// <summary>
        /// 长度分钟(支持mp4?)
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string GetMediaTimeLenMinute(string path) {
            throw new NotImplementedException();
            //try {
            //    Shell32.Shell shell = new Shell32.Shell();
            //    //文件路径               
            //    Shell32.Folder folder = shell.NameSpace(path.Substring(0, path.LastIndexOf("\\")));
            //    //文件名称             
            //    Shell32.FolderItem folderitem = folder.ParseName(path.Substring(path.LastIndexOf("\\") + 1));
            //    if (Environment.OSVersion.Version.Major >= 6) {
            //        string mediaLength = folder.GetDetailsOf(folderitem, 27);
            //        return mediaLength;
            //    } else {
            //        string mediaLength = folder.GetDetailsOf(folderitem, 21);
            //        return mediaLength;
            //    }
            //} catch (Exception) {
            //    return null;
            //}
        }
        /// <summary>
        /// 长度秒(支持mp4?)
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static int GetMediaTimeLenSecond(string path) {
            throw new NotImplementedException();
            //try {
            //    Shell32.Shell shell = new Shell32.Shell();
            //    //文件路径               
            //    Shell32.Folder folder = shell.NameSpace(path.Substring(0, path.LastIndexOf("\\")));
            //    //文件名称             
            //    Shell32.FolderItem folderitem = folder.ParseName(path.Substring(path.LastIndexOf("\\") + 1));
            //    string len;
            //    if (Environment.OSVersion.Version.Major >= 6) {
            //        len = folder.GetDetailsOf(folderitem, 27);
            //    } else {
            //        len = folder.GetDetailsOf(folderitem, 21);
            //    }

            //    string[] str = len.Split(new char[] { ':' });
            //    int sum = 0;
            //    sum = int.Parse(str[0]) * 60 * 60 + int.Parse(str[1]) * 60 + int.Parse(str[2]);

            //    return sum;
            //} catch (Exception) { 
            //    return 0;
            //}
        }
    }
}
